package com.vains.config.security;

import com.vains.bean.Result;
import com.vains.constant.ErrorCodeConstants;
import com.vains.filter.ClientTokenEndpointFilter;
import com.vains.util.ServletUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.exceptions.*;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.web.HttpRequestMethodNotSupportedException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;

/**
 * 授权服务配置
 *
 * @author vains
 * @date 2021/4/3 0:31
 */
@Slf4j
@Order(2)
@Configuration
@AllArgsConstructor
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    private final DataSource dataSource;

    private final TokenStore tokenStore;

    private final UserDetailsService userDetailsService;

    private final AuthenticationManager authenticationManager;

    private final AuthorizationCodeServices authorizationCodeServices;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 客户端信息配置
        clients.jdbc(dataSource);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        // 端点配置
        // token 存储方式
        endpoints.tokenStore(tokenStore);
        // 授权码存储方式
        endpoints.authorizationCodeServices(authorizationCodeServices);
        // 密码管理器
        endpoints.authenticationManager(authenticationManager);
        // 注入刷新token所需的 UserDetailsService
        endpoints.userDetailsService(userDetailsService);
        // 设置认证服务器异常处理
        endpoints.exceptionTranslator(new WebExceptionTranslator());
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        // 获取token key和检测token不需登录
        security.tokenKeyAccess("permitAll()");
        security.checkTokenAccess("permitAll()");
        // 未携带token访问受保护资源处理
        security.authenticationEntryPoint(this::authenticationEntryPoint);
        // 添加自定义过滤器处理 client_id/client_secret 错误时的提示，
        // 注意：不能调用 security.allowFormAuthenticationForClients(); 否则security会创建一个过滤器覆盖本次设置
        ClientTokenEndpointFilter clientTokenEndpointFilter = new ClientTokenEndpointFilter(security);
        clientTokenEndpointFilter.afterPropertiesSet();
        clientTokenEndpointFilter.setAuthenticationEntryPoint(this::clientCredentialsEntryPoint);
        security.addTokenEndpointAuthenticationFilter(clientTokenEndpointFilter);
    }

    /**
     * client_id/client_secret 错误 自定义响应
     * @param request 请求
     * @param response 响应
     * @param e 具体的异常信息
     */
    private void clientCredentialsEntryPoint(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
        log.error("client_id或client_secret 错误.");
        String message = "client_id或client_secret 错误，请检查之后重试.";
        String errorDetail = "Bad client credentials";
        OAuth2Exception exception = OAuth2Exception.create(OAuth2Exception.INVALID_CLIENT, errorDetail);
        Result<OAuth2Exception> oAuth2ExceptionResult = Result.oauth2Error(ErrorCodeConstants.CLIENT_INFO_ERROR, message, exception);
        ServletUtils.writeDataByResponse(response, oAuth2ExceptionResult);
    }

    /**
     * 未登录，登录失败异常处理
     * @param request 请求
     * @param response 响应
     * @param e 具体的异常
     */
    private void authenticationEntryPoint(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
        log.error("未登录.");
        String message = "请携带token访问该接口.";
        OAuth2Exception exception = OAuth2Exception.create(OAuth2Exception.ACCESS_DENIED, e.getMessage());
        Result<OAuth2Exception> oAuth2ExceptionResult = Result.oauth2Error(message, exception);
        ServletUtils.writeDataByResponse(response, oAuth2ExceptionResult);
    }

    /**
     * 处理Oauth2异常的类，将异常转为自定义公共响应类
     */
    static class WebExceptionTranslator implements WebResponseExceptionTranslator {

        @Override
        public ResponseEntity<?> translate(Exception e) {
            String message = null;
            OAuth2Exception exception = null;
            Integer errorCode = null;
            if (e instanceof InvalidClientException) {
                message = e.getMessage();
                exception = OAuth2Exception.create(OAuth2Exception.INVALID_CLIENT, message);
            } else if (e instanceof UnauthorizedClientException) {
                message = e.getMessage();
                exception = OAuth2Exception.create(OAuth2Exception.UNAUTHORIZED_CLIENT, message);
            } else if (e instanceof InvalidGrantException) {
                String badCredentials = "Bad credentials";
                String invalidRefreshToken = "Invalid refresh token";
                if (badCredentials.equals(e.getMessage())) {
                    message = "密码错误.";
                    errorCode = ErrorCodeConstants.USER_PASSWORD_ERROR;
                } else if (e.getMessage().startsWith(invalidRefreshToken)){
                    message = "刷新token失败.";
                    errorCode = ErrorCodeConstants.REFRESH_TOKEN_ERROR;
                } else {
                    message = "授权码错误.";
                    errorCode = ErrorCodeConstants.INVALID_AUTHORIZATION_CODE;
                }
                exception = OAuth2Exception.create(OAuth2Exception.INVALID_GRANT, e.getMessage());
            } else if (e instanceof InvalidScopeException) {
                message = e.getMessage();
                exception = OAuth2Exception.create(OAuth2Exception.INVALID_SCOPE, message);
            } else if (e instanceof InvalidTokenException) {
                OAuth2Exception oAuth2Exception = OAuth2Exception.create(OAuth2Exception.INVALID_TOKEN, e.getMessage());
                return new ResponseEntity<>(oAuth2Exception, HttpStatus.OK);
            } else if (e instanceof InvalidRequestException) {
                message = e.getMessage();
                exception = OAuth2Exception.create(OAuth2Exception.INVALID_REQUEST, message);
            } else if (e instanceof RedirectMismatchException) {
                message = "回调地址 redirect_uri 参数异常.";
                exception = OAuth2Exception.create(OAuth2Exception.REDIRECT_URI_MISMATCH, e.getMessage());
                errorCode = ErrorCodeConstants.INVALID_REDIRECT_URL;
            } else if (e instanceof UnsupportedGrantTypeException) {
                int splitLength = 2;
                String[] split = e.getMessage().split(":");
                if (split.length == splitLength) {
                    message = "参数grant_type有误：grant type[" + split[1] + " ]不存在.";
                    errorCode = ErrorCodeConstants.GRANT_TYPE_ERROR;
                }
                exception = OAuth2Exception.create(OAuth2Exception.UNSUPPORTED_GRANT_TYPE, e.getMessage());
            } else if (e instanceof UnsupportedResponseTypeException) {
                message = e.getMessage();
                exception = OAuth2Exception.create(OAuth2Exception.UNSUPPORTED_RESPONSE_TYPE, message);
            } else if (e instanceof UserDeniedAuthorizationException) {
                message = e.getMessage();
                exception = OAuth2Exception.create(OAuth2Exception.ACCESS_DENIED, message);
            } else if (e instanceof AccessDeniedException) {
                message = "权限不足.";
                exception = OAuth2Exception.create(OAuth2Exception.ACCESS_DENIED, message);
            } else if (e instanceof BadClientCredentialsException) {
                // 如果走这个异常，目前发现只有 在授权码模式中，携带错误的client_id时会触发该异常
                message = "客户端不存在.";
                exception = OAuth2Exception.create(OAuth2Exception.INVALID_CLIENT, e.getMessage());
                errorCode = ErrorCodeConstants.CLIENT_INFO_ERROR;
            } else if (e instanceof InternalAuthenticationServiceException) {
                // 用户不存在
                message = e.getMessage();
                exception = OAuth2Exception.create(OAuth2Exception.ACCESS_DENIED, message);
                errorCode = ErrorCodeConstants.USER_NOT_FOUND;
            } else if (e instanceof HttpRequestMethodNotSupportedException) {
                // 请求方式错误
                message = e.getMessage();
                exception = OAuth2Exception.create(OAuth2Exception.INVALID_REQUEST, message);
                errorCode = ErrorCodeConstants.REQUEST_NOT_SUPPORT;
            }
            Result<OAuth2Exception> oAuth2ExceptionResult = errorCode == null ? Result.oauth2Error(message, exception) : Result.oauth2Error(errorCode, message, exception);
            return new ResponseEntity<>(oAuth2ExceptionResult, HttpStatus.OK);
        }
    }

}
